/*
 * Copyright 1999-2004 Carnegie Mellon University.
 * Portions Copyright 2004 Sun Microsystems, Inc.
 * Portions Copyright 2004 Mitsubishi Electric Research Laboratories.
 * All Rights Reserved.  Use is subject to license terms.
 *
 * See the file "license.terms" for information on usage and
 * redistribution of this file, and for a DISCLAIMER OF ALL
 * WARRANTIES.
 *
 */

package edu.cmu.sphinx.demo.XMLParser;


import edu.cmu.sphinx.frontend.util.Microphone;
import edu.cmu.sphinx.recognizer.Recognizer;
import edu.cmu.sphinx.result.Result;
import edu.cmu.sphinx.util.props.ConfigurationManager;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedList;
 
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import sun.security.action.GetBooleanAction;

import com.sun.speech.freetts.Voice;
import com.sun.speech.freetts.VoiceManager;

public class XmlParser {

    public static void main(String[] args) throws IOException {
        
    	ArrayList<Var> variables = new ArrayList<Var>();
    	ArrayList<Prompt> prompts = new ArrayList<Prompt>();
    	
    	parse(prompts,variables);
        
         BufferedReader str = new BufferedReader(new InputStreamReader(System.in));
         
         System.out.println("You have 2 options:");
         System.out.println("0: Microphone");
         System.out.println("1: Keyboard");
         
         if(str.readLine().equals("0")){
         Recognizer recognizer = initializeRecognizer(args);
         textInput(str,prompts,recognizer,false);
         recognizer.deallocate();
         System.exit(1);             
         }
         else{
         textInput(str,prompts,null,true);            
         }
         
    }

    static void speak(String string) {
                
        String voiceName = "kevin16";
            
        VoiceManager voiceManager = VoiceManager.getInstance();
        Voice helloVoice = voiceManager.getVoice(voiceName);
        
        /* Allocates the resources for the voice.
         */
        helloVoice.allocate();
            
        /* Synthesize speech.
         */
        helloVoice.speak(string);
        
        /* Clean up and leave.
         */
        helloVoice.deallocate();
            
    }

    private static Recognizer initializeRecognizer(String[] args) {
        ConfigurationManager cm;

        if (args.length > 0) {
            cm = new ConfigurationManager(args[0]);
        } else {
            cm = new ConfigurationManager(
                    XmlParser.class.getResource("helloworld.config.xml"));
        }

        Recognizer recognizer = (Recognizer) cm.lookup("recognizer");

        recognizer.allocate();

        // start the microphone or exit if the programm if this is not possible
        Microphone microphone = (Microphone) cm.lookup("microphone");

        if (!microphone.startRecording()) {
            System.out.println("Cannot start microphone.");
            recognizer.deallocate();
            System.exit(1);
        }
        System.out.println("Microphone Started");
        System.out.println("Recognizer READY !");

        return recognizer;
    }

    private static String getText(boolean b, Recognizer recognizer, BufferedReader str) throws IOException {
                
        if (b) {
            return str.readLine();
        } {
            String s = asr(recognizer);

            // System.out.println("You said : "+s);
            return s;
        }
                
    }

    private static int calculatePrice(LinkedList<String> answers) {
                
        int result = 20;

        if (answers.contains("big")) {
            result += 5;
        }
        if (answers.contains("regular")) {
            result += 2;
        }
        if (answers.contains("cheese")) {
            result += 1;
        }
        if (answers.contains("mushroom")) {
            result += 2;
        }
        if (answers.contains("peporoni")) {
            result += 3;
        }
        if (answers.contains("rustic")) {
            result += 4;
        }
        if (answers.contains("pan")) {
            result += 5;
        }
        if (answers.contains("thin")) {
            result += 2;
        }
                
        return result;
    }

    private static void textInput(BufferedReader str, ArrayList<Prompt> pr, Recognizer recognizer, boolean b) {
	            
	    LinkedList<String> answers = new LinkedList<String>();
	            
	    try {
	            
	        for (int i = 0; i < pr.size(); i++) {
	            Prompt p = pr.get(i);
	                    
	            if (p.type == Prompt.BLOCK) {
	                int price = 0;
	
	                if (i == pr.size() - 1) {
	                    price = calculatePrice(answers);
	                    p.updateResult(price);
	                }
	                            
	                System.out.println(p.prompt);
	                speak(p.prompt);
	            } 
	            
	            else if (p.type == Prompt.QUESTION) {
	                
	            	System.out.println(p.prompt);
	                speak(p.prompt);
	                p.printAnswers();
	                
	                String ans = getText(b, recognizer, str);
	
	                if (!p.answers.contains(ans)) {
	                    
	                	if(ans.isEmpty() && !p.noInput.isEmpty()) 
	                    	speak(p.noInput);
	                    
	                	else if(!p.noMatch.isEmpty())
	                    	speak(p.noMatch);
	                	
	                	i--;
	                }
	                //filled
	                else {
	                  
	                  answers.add(ans);
	                  Prompt next = p.evalCond(ans);
	                  
	                  if(next != null){
	
	                  System.out.println(next.prompt);
	                  speak(next.prompt);
	                  
	                  }
	                
	                }
	                            
	            } 
	            
	            else if(p.type == Prompt.CONFIRM) {
	            	
	                String temp = p.getupdatedConfirmation(answers.getLast());
	
	                System.out.println(temp);
	                speak(temp);
	                p.printAnswers();
	                boolean confirm = getConfirmation(getText(b, recognizer, str));
	
	                if (!confirm) {
	                    answers.removeLast();
	                    i -= 2;
	                }
	            }
	            
	            else if(p.type == Prompt.GOTO){
	            	int index = getByFieldName(p.prompt, pr);
	            	i = index-1;
	            }
	            
	            else if(p.type == Prompt.AUDIO){
	            	playSound(p.prompt);
	            }
	        }
	    } catch (Exception e) {
	        e.printStackTrace();
	    }
	            
	}

	private static boolean getConfirmation(String readLine) {
        if (readLine.equals("yes")) {
            return true;
        }
        return false;
    }
    
    private static int getByFieldName(String name, ArrayList<Prompt> pr){
    	
    	for(int i=0; i<pr.size(); i++)
    		if(pr.get(i).field!=null && pr.get(i).field.equals(name)){
    			return i;
    		}
    	return 0;
    }

    private static String asr(Recognizer recognizer) {
                
        Result result = recognizer.recognize();
        String resultText = "";

        if (result != null) {
            resultText = result.getBestFinalResultNoFiller();                
        } else {
            resultText = "";
        }
            
        return resultText;
        
    }

    private static ArrayList<Prompt> parse(ArrayList<Prompt> p, ArrayList<Var> variables) {
          
        try {
        	
        	File fXmlFile = new File("dialog1.xml");
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            Document doc = dBuilder.parse(fXmlFile);

            doc.getDocumentElement().normalize();
            Node root = doc.getElementsByTagName("vxml").item(0);
            NodeList rootList = root.getChildNodes();
              
            for (int m = 0; m < rootList.getLength(); m++) {
                Node n = rootList.item(m);

                if (n.getNodeName().equals("form")) {
                    NodeList nl = n.getChildNodes();
                    Prompt pm;
                    Node current;

                    for (int i = 0; i < nl.getLength(); i++) {
                        current = nl.item(i);
                                                
                        if (current.getNodeName().equals("block")) {
                        	NodeList bL = current.getChildNodes();
                        	NamedNodeMap a = current.getAttributes();
                            String att;
                            if(a.getLength()>0)
                            	att = a.item(a.getLength()-1).toString();
                            else
                            	att = "";
                            
                            String blockName = att.replace("name=", "");
                            blockName = blockName.replace("\"", "");
                            
                            boolean hasNochildren = true;
                        	
                        	for(int k = 0;k < bL.getLength();k++){
                        		if(bL.item(k).getNodeName().equals("prompt")){
                        			pm = new Prompt(bL.item(k).getTextContent(), Prompt.BLOCK);
                                    p.add(pm);
                                    pm.field = blockName;
                                    hasNochildren = false;
                                }
                        		else if(bL.item(k).getNodeName().equals("goto")){
                        			NamedNodeMap attributes = bL.item(k).getAttributes();
                                    att = attributes.item(attributes.getLength()-1).toString();
                                    String name = att.split("=")[0];
                                    String value = att.split("=")[1];
                                    value = value.replace("\"", "");
                                    value = value.replace("\'", "");
                                        
                                    Prompt go;
                                    if(name.equals("nextitem")){
                                        go= new Prompt(value,Prompt.GOTO);
                                    }
                                    
                                    else{
                                        value = getVarByName(value,variables).value;
                                        go= new Prompt(value,Prompt.GOTO);
                                    }
                                    p.add(go);
                        			hasNochildren = false;
                        		}
                        		else if(bL.item(k).getNodeName().equals("audio")){
                        			
                        			NamedNodeMap attributes = bL.item(k).getAttributes();
                                    att = attributes.item(attributes.getLength()-1).toString();
                                    String name = att.split("=")[0];
                                    String value = att.split("=")[1];
                                    value = value.replace("\"", "");
                                    value = value.replace("\'", "");
                                    
                                    Prompt audio;
                                    if(name.equals("src")){
                                        audio= new Prompt(value,Prompt.AUDIO);
                                    }
                                    else {
                                    	audio = null;
                                    }
                                    
                                    p.add(audio);
                        			hasNochildren = false;
                        			
                        		}
                        		
                        	}
                        	
                        	if(hasNochildren){
                                pm = new Prompt(nl.item(i).getTextContent(),Prompt.BLOCK);
                                p.add(pm);
                                pm.field = blockName;
                            }
                            
                        }
                        	
                        	                         
                        
                       else if (current.getNodeName().equals("field")) {
                            
                        	NodeList nl2 = nl.item(i).getChildNodes();
                        	processField(current,p,nl2);
                        }
                    }
                }
                else if(n.getNodeName().equals("var")){
                	Var variable = new Var();
                	NamedNodeMap attributes = n.getAttributes();
                	
                	for(int ii=0; ii<attributes.getLength();ii++){
                		String att = attributes.item(ii).toString();
                		if(att.contains("name")){
                			variable.name = att.split("=")[1].replace("\"","");
                			
                		}
                		else{
                			variable.value = att.split("=")[1].replace("\"","");
                			variable.value = variable.value.replace("\'","");
                			
                		}
                	}
                	variables.add(variable);
                    
                }
               
            }
        }
                        	
         catch (Exception e) {
            e.printStackTrace();
        }
         for (int i = 0; i < p.size(); i++) 
             //System.out.println
             p.get(i).toString();
                  
        return p;
                
    }
        
    private static Var getVarByName(String value, ArrayList<Var> variables) {
		for(int i = 0; i< variables.size(); i++){
			if(variables.get(i).name.equals(value)){
				return variables.get(i);
			}
		}
		return null;
	}

	private static void processField(Node current, ArrayList<Prompt> p, NodeList nl2) {
    	
    	Prompt pm; 
    	
    	for(int j = 0; j < nl2.getLength(); j++){
            
            Node currentNode = nl2.item(j);
            String currentName = currentNode.getNodeName();
            String att = current.getAttributes().item(current.getAttributes().getLength()-1).toString();
            
            if(currentName.equals("prompt")){
            	if(checkAttribute(att.toString())){
            		pm = new Prompt(currentNode.getTextContent(),Prompt.CONFIRM);
            	}
            	
            	else{
            	
            		pm = new Prompt(currentNode.getTextContent(),Prompt.QUESTION);
            	
            	}
            	pm.field = att.replace("name=", "");
            	pm.field = pm.field.replace("\"", "");
                p.add(pm);
            }
                        
            else if(currentName.equals("grammar")){
            	NodeList answers = currentNode.getChildNodes().item(1).getChildNodes().item(1).getChildNodes();
                            
                for(int k = 0; k < answers.getLength(); k++){
                	ArrayList<String> promptAnswers = p.get(p.size()-1).answers; 
                    promptAnswers.add(answers.item(k).getTextContent().split(" ")[0]);
                    int last = promptAnswers.size()-1;
                            
                    if(promptAnswers.get(last).contains(" ")){
                    	promptAnswers.remove(last);
                    }
                }
            }
            
            else if(currentName.equals("noinput") || currentName.equals("nomatch")){
                    
                    boolean type;
                    if(currentName.equals("noinput"))
                            type = true;
                    else
                            type = false;
                    
                    NodeList nl3 = currentNode.getChildNodes();
                    
                    for(int k=0; k<nl3.getLength(); k++){
                    	
                    	if(nl3.item(k).getNodeName().equals("prompt")){
                    		
                    		pm = p.get(p.size()-1);
                            String text = nl3.item(k).getTextContent();
                    		
                            if(type){
                            	pm.noInput = text;
                            }
                            else{
                            	pm.noMatch = text;
                            }
                    	}
                    }
            }
            
            else if(currentName.equals("filled")){
            
            NodeList nl4 = currentNode.getChildNodes();
            
            for(int k=0; k<nl4.getLength(); k++){
            	
            	String nameFilled = nl4.item(k).getNodeName();
            	
            	if(nameFilled.equals("if")){
                
            	
            	
            	NodeList bl = nl4.item(k).getChildNodes();
            	processIf(nl4.item(k),p,bl,0);
            	
            	for(int kk=0; kk<bl.getLength(); kk++){
            		processIf(bl.item(kk),p,bl,kk);
            		
            	}
            	            	            
            }
            	
          }
            
       }
    	
   }
}
	
	private static void playSound(String file){
		
		File soundFile = new File(file);
		AudioInputStream audioIn;
		try {
			audioIn = AudioSystem.getAudioInputStream(soundFile);
			Clip clip = AudioSystem.getClip();
			clip.open(audioIn);
			clip.start();
			
		} catch (UnsupportedAudioFileException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (LineUnavailableException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
		
	}
           
	
    private static void processIf(Node item, ArrayList<Prompt> p, NodeList bl, int kk) {
    	
    	if(!item.getNodeName().equals("if") && !item.getNodeName().equals("elseif"))
    		return;
    	
    	NamedNodeMap attributes = item.getAttributes();
        String cond = attributes.item(attributes.getLength()-1).toString();
        int type = 0;
        Prompt condition = null;
        String answer = null;
    	String promptName;
    	
        if(cond.contains("==")){
        	type = If.EQUAL;
        	promptName = cond.split("==")[0].replace("cond=\"", "");
        	condition = getPromptByName(p,promptName);
            answer = cond.split("==")[1];
        }
        else if(cond.contains("!=")){
        	type = If.NOTEQUAL;
        	promptName = cond.split("!=")[0].replace("cond=\"", "");
        	condition = getPromptByName(p,promptName);
            answer = cond.split("!=")[1];
        }
        
        if(answer != null){
        	answer = answer.replace("\"", "");
        	answer = answer.replace("\'", "");
        }
        if(condition != null){
        Prompt promptE;
        String text = "";
        for(int i = kk; i < bl.getLength();i++){
        	if(bl.item(i).getNodeName().equals("prompt")){
        		text = bl.item(i).getTextContent();
        		break;
        	}
        }
        //int length = item.getChildNodes().getLength();
        //String text = item.getChildNodes().item(length-1).
        promptE = new Prompt(text,Prompt.BLOCK);
        
        If iCondtion= new If();
        iCondtion.answer = answer;
        iCondtion.condtitionType = type;
        iCondtion.promptE = promptE;
        condition.ifConditions.add(iCondtion);
    }
        }        

	

	private static boolean checkAttribute(String att) {
        return att.contains("confirm");
    }
    
    private static Prompt getPromptByName(ArrayList<Prompt> prompts, String name) {
    	
    	for (int i = 0; i < prompts.size(); i++) {
        	String field = prompts.get(i).field;
            //System.out.println(field);
        	if (field != null && field.equals(name)) {
                return prompts.get(i);
            }
        }
        return null;
    }
}

class Var{
	String name;
	String value;
}

class Goto{
	static final int VAR = 1;
	static final int VALUE = 2;
	
	int type;
	String value;
	
}

class If {
    static final int EQUAL = 10;
    static final int NOTEQUAL = 20;
    Prompt promptE; // prompt that should be executed in case of if condition.
    int condtitionType;
    String answer;
    public If() {}
}

        
class Prompt {
    public static final int AUDIO = 0;
	static final int BLOCK = 1;
    static final int QUESTION = 2;
    static final int CONFIRM = 3;
    static final int GOTO = 4;
            
    String prompt;
    ArrayList<String> answers;
    ArrayList<If> ifConditions;
    String noInput;
    String noMatch;
    
    String recentAnswer;
    
    // ArrayList<Prompt> conditions;
        
    int type;
    String field;
    public Prompt(String prompt, int type) {
        this.prompt = prompt;
        answers = new ArrayList<String>();
        ifConditions = new ArrayList<If>();
        this.type = type;
        noInput = noMatch = "";
    }
        
    public String toString() {
        String s = "";

        s += prompt + " ";
        s += type + " ";
        s += field + " ";
        s += ifConditions.size()+" ";
        s += "[";
        for (int i = 0; i < answers.size(); i++) {
            if (!answers.get(i).contains("\n")) {
                s += answers.get(i) + ",";
            } else {
                answers.remove(i);
                i--;
            }
        }
        s = s.substring(0, s.length() - 1);
        s += "]";
        return s;
    }

    public void printAnswers() {
                
        for (int i = 0; i < answers.size(); i++) {
                        
            System.out.println(" - " + answers.get(i));
            XmlParser.speak(answers.get(i));
        }       
                
    }

    public String getupdatedConfirmation(String value) {
                
        return prompt.replace("a ", "a " + value);
                
    }

    public void updateResult(int price) {
                
        prompt = prompt.replace("is ", "is " + price);
                
    }
        
    public Prompt evalCond(String answer) {
        
    	if (ifConditions.size() == 0) {
            return null;
        } 
    	else {
    		for (int i = 0; i < ifConditions.size(); i++) {
                If ifCond = ifConditions.get(i);
                
                if (ifCond.condtitionType == If.EQUAL) {
                    if (ifCond.answer.equals(answer)) {
                        return ifCond.promptE;
                    }
                } else if (ifCond.condtitionType == If.NOTEQUAL) {
                    if (!ifCond.answer.equals(answer)) {
                        return ifCond.promptE;
                    }
                }
            }
    	}
    	return null;
    }
        
}

